package com

import kotlin.reflect.*
import kotlin.reflect.full.functions
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl

fun <T : Any> KotlinJsr223ScriptEngineImpl.getVarValue(name: String): T? {
    @Suppress("UNCHECKED_CAST")
    return getVar(name).firstOrNull()?.get() as? T
}
fun KotlinJsr223ScriptEngineImpl.getVar(name: String): List<ScriptField> {
    return this.backwardInstancesHistory.mapNotNull {
        it::class.memberProperties.find { find ->
            find.name == name
        }?.let { v ->
            v.isAccessible = true
            ScriptField(v, it)
        }
    }.toList()
}

fun KotlinJsr223ScriptEngineImpl.getVar(name: String, type: KType): List<ScriptField> {
    return this.backwardInstancesHistory.mapNotNull {
        it::class.memberProperties.find { find ->
            find.returnType == type && find.name == name
        }?.let { v ->
            v.isAccessible = true
            ScriptField(v, it)
        }
    }.toList()
}

fun KotlinJsr223ScriptEngineImpl.getFunction(name: String): List<ScriptMethod> {
    return this.backwardInstancesHistory.mapNotNull {
        it::class.functions.find { find ->
            find.name == name
        }?.let { v ->
            ScriptMethod(v, it)
        }
    }.toList()
}

fun KotlinJsr223ScriptEngineImpl.getFunction(
    name: String,
    vararg types: KTypeParameter
): List<ScriptMethod> {
    return this.backwardInstancesHistory.mapNotNull {
        it::class.functions.find { find ->
            find.typeParameters.size == types.size
                    && isValidAll(find.typeParameters, types.toList())
                    && find.name == name
        }?.let { v ->
            ScriptMethod(v, it)
        }
    }.toList()
}

fun <T> isValidAll(list: List<T>, list2: List<T>): Boolean {
    list.forEachIndexed { index, t ->
        if (t != list2[index]) {
            return false
        }
    }
    return true
}

data class ScriptField(
    val property: KProperty1<out Any, *>,
    val target: Any
) {

    fun get(): Any? {
        return property.getter.call(target)
    }

    fun set(value: Any) {
        if (property is KMutableProperty1) {
            property.setter.call(target, value)
        }
    }

}

data class ScriptMethod(
    val function: KFunction<*>,
    val target: Any? = null
) {

    fun call(vararg args: Any?): Any? {
        return if(target == null) function.call(*args) else function.call(target, *args)
    }

}